/**
 * Author : Fengyuan(Franklin) Zhang
 * Date : 2019/1/23
 * Description : Servers control
 */

var ControlBase = require('./controlBase');
var ServersModel = require('../models/servers');
var ModelServiceCollectionModel = require('../models/modelServiceCollection');
// var TaskCtrl = require('./task');
// modify by qingbin
var CommonService = require('../service/CommonService');
var Setting = require('../setting');
var request = require('request');
var modelServiceSDK = require('modelservicesdk');
var Schedule = require('node-schedule');
const _ = require("lodash");

// var RegisterCtrl = require('./register');
const Server = require('modelservicesdk/src/server');
const Servers = require('../models/servers');

 
var ServersCtrl = function() {};
ServersCtrl.__proto__ = ControlBase;
ServersCtrl.model = ServersModel;

module.exports = ServersCtrl;

//! init function
ServersCtrl.init = function(){
    var rule = new Schedule.RecurrenceRule();
    rule.minute = [0,5,10,15,20,25,30,35,40,45,50,55];
    Schedule.scheduleJob(rule, ()=>{
        ServersCtrl.checkServerStatus(function(err, status){
            if(err){
                console.log(err);
            }
            console.log('check server status finished at : ' + new Date());
        })
    })
}

ServersCtrl.getById = function(id,callback){
    ServersModel.getByOID(id, this.returnFunction(callback, 'Error in get server by id'));
}

ServersCtrl.getAll = function(callback){
    ServersModel.getAll(this.returnFunction(callback, 'Error in get all server'));
}

//! get server by mac
ServersCtrl.getByMac = function (mac, callback) {
    ServersModel.getByMac(mac, this.returnFunction(callback, 'Error in getting mac address!'));
}

//! get server by PID
ServersCtrl.getByPID = function (pid, callback) {
    ServersModel.getByPID(pid, this.returnFunction(callback, 'Error in getting servers by PID'));
}

//! get server by IP
ServersCtrl.getByIP = function (ip, callback){
    ServersModel.getByIP(ip, this.returnFunction(callback, 'Error in getting servers by IP'));
}

//! send task
ServersCtrl.sendTask = function (server, taskInfo, servers, callback) {
    request.post('http://' + server.s_ip + ':' + server.s_port + '/task', {form : taskInfo}, function(err, data){
        if(err){
            //! if any error, system will pick next suitable computing node
            if(servers.length > 0){
                var server_s = servers.splice(0, 1);
                ServersCtrl.sendTask(server_s[0], taskInfo, servers, function (err, data) {
                    return callback(err, data);
                });
            }
            else{
                return callback(err);
            }
        }
        else{
            var body = JSON.parse(data.body);
            if(body.result == 'suc'){
                return callback(null, {
                    server : server,
                    msrid : body.data
                });
            }
            else{
                if(servers.length == 0){
                    return callback(new Error("No more servers!"))
                }
                var server_s = servers.splice(0, 1);
                ServersCtrl.sendTask(server_s[0], taskInfo, servers, function (err, data) {
                    return callback(err, data);
                });
            }
        }
    });
}

//! update server model service list
ServersCtrl.updateService = function (server, callback) {
    var server_contianer = modelServiceSDK.createServer(server.s_ip, server.s_port);
    server_contianer.connect().then(
        (data) => {
            return server_contianer.getServiceAccess();
        },
        (err) => {
            console.log('Err');
            return callback(err);
        }
    )
    .then(
        (access) => {
            return access.getModelServicesList();
        }
    )
    .then(
        (data) => {
            var services = [];
            for(var i = 0; i < data.length; i++){
                if(data[i].status == 1){
                    services.push(data[i].pid);
                }
            }
            server.s_services = services;
            ServersCtrl.update(server, this.returnFunction(callback, 'Error in updating services!'));
        }
    );
}

//! check ip with mac
ServersCtrl.checkIP = function (ip, mac, callback) {
    //! search server by IP. If no such server, switch to search it by mac. If get it, modify server's IP. If still not, callback with false
    ServersModel.getByIP(ip, function (err, server) {
        if(err){
            return callback(err);
        }
        if(server.length == 0 || server[0].s_mac != mac){
            ServersModel.getByMac(mac, function (err, server_n) {
                if(err){
                    return callback(err);
                }
                if(server_n.length == 0){
                    return callback(null, false);
                }
                server_n = server_n[0];
                server_n.s_ip = ip;
                server_n.s_datetime = new Date();
                server_n.s_status = true;
                ServersModel.update(server_n, function (err, result) {
                    if(err){
                        return callback(err);
                    }
                    return callback(null, server_n);
                });
            })
        }
        else{
            return callback(null, server[0]);
        }
    });
}

//! post model services by ip and mac
ServersCtrl.postServices = function (ip, mac, pids, callback) {
    ServersCtrl.checkIP(ip, mac, function (err, server) {
        if(err){
            return callback(err);
        }
        if(server == false){
            return callback(new Error('No such server'));
        }
        server.s_services = pids;
        ServersModel.update(server, function (err, result) {
            if(err){
                return callback(err);
            }
            return callback(null, true);
        });
    });
}

//! insert a new service
ServersCtrl.insertAService = function (server, pid, callback) {
    var i = 0;
    for(i = 0; i < server.s_services.length; i++){
        if(server.s_services[i] == pid){
            break;
        }
    }
    if(i == server.s_services.length){
        server.s_services.push(pid);
        ServersCtrl.update(server, this.returnFunction(callback, 'Error in updating server!'));
    }
    else{
        return callback(null, true);
    }
}

//! remove a service
ServersCtrl.removeAService = function (server, pid, callback) {
    var i = 0;
    for(i = 0; i < server.s_services.length; i++){
        if(server.s_services[i] == pid){
            server.s_services.splice(i, 1);
            break;
        }
    }
    ServersCtrl.update(server, this.returnFunction(callback, 'Error in updating server!'));
}

//! get all available model services !!! Local network only
ServersCtrl.getAllModelServices = function(callback){
    ServersModel.getAll(function(err, servers){
        if(err){
            return callback(err);
        }

        //! get all local network servers
        var servers_local = [];
        for(var i = 0; i < servers.length; i++){
            if(servers[i].s_type == 1){
                servers_local.push(servers[i]);
            }
        }

        if(servers_local.length == 0){
            return callback(null, new ModelServiceCollectionModel());
        }
        
        //! finishing callback
        var finishing = function () {
            var msc = new ModelServiceCollectionModel();
            for(var i = 0; i < servers_local.length; i++){
                for(var j = 0; j < servers_local[i].s_ms.length; j++){
                    msc.insertModelService(servers_local[i].s_ms[j]);
                }
            }
            return callback(null, msc);
        }

        var count = 0;
        var cb = function (index) {
            count ++;
            return function (error, response, body) {
                count --;
                if(err){
                    return;
                }
                else{
                    var resJson = JSON.parse(body);
                    if(resJson['result'] == 'suc'){
                        servers_local[index].s_ms = resJson['data'];
                    }
                    else{
                        servers_local[index] = [];
                    }
                }
                if(count == 0){
                    finishing();
                }
            }
        }
        
        //! get all model services
        for(var i = 0; i < servers_local.length; i++){
            request('http://' + servers_local[i].s_ip + ':' + servers_local[i].s_port.toString() + '/modelser/json/all', cb(i));
        }
    });

}

//! get model service container status with mac address
ServersCtrl.getStatusByMac = function(mac,callback){
    ServersCtrl.getByMac(mac,function(err,server){
        var url = 'http://' + server.s_ip + ':' + server_s.s_port + '/ping';
        ServersCtrl.pingAndUpdate(url, function(err, data){
            if(err){
                return callback(null, false);
            }
            if(data){
                return callback(null,true);
            }else{
                return callback(null,false);   
            }
        })
    })
}

//! check server status (include two condition: Local network and Internet)
ServersCtrl.checkServerStatus = function(callback){
    ServersModel.getAll(function(err,servers){
        if(err){
            return callback(err);
        }
        var size = servers.length;
        var count = 0;
        if(size){
            //build the callback
            var pending = function(index){
                count++;
                return function(err, result){
                    count--;
                    if(err){
                        return callback(err);
                    }
                    if(count == 0){
                        return callback(null,true);
                    }
                }
            }

            for(var i = 0; i < size; i++){
                //judge from the type, Local network and Internet have different judge method
                var type = servers[i].s_type;
                //type=1 表示 模型容器是 local network类型
                if(type == 1){
                    var url = 'http://' + servers[i].s_ip + ':' + servers[i].s_port + '/ping';
                    ServersCtrl.pingAndUpdate(url, servers[i], pending(i));
                }else{
                    var nowTime = new Date();
                    var pastTime = new Date(servers[i].s_datetime);
                    var dateDiff = nowTime.getTime() - pastTime.getTime();
                    var minutes = Math.floor(dateDiff /(60*1000));
                    // 8 minutes means the server has been offline
                    if(minutes >= 8){
                        servers[i].s_status = false;
                        ServersModel.update(servers[i], pending(i));
                        //更新状态信息到门户(异步任务) !!! To be validated
                        ServersCtrl.updateContainerStatusToPortal(servers[i], false, 3000, function (err, status) {
                            if (err) {
                                console.log(err);
                            }
                            if (status) {
                                console.log('update model container status success!');
                            } else {
                                console.log('update model container status fail!');
                            }
                        });
                    } else {
                        servers[i].s_status = true;
                        ServersModel.update(servers[i], pending(i));
                        //更新状态信息到门户(异步任务) !!! To be validated
                        ServersCtrl.updateContainerStatusToPortal(servers[i], true, 3000, function (err, status) {
                            if (err) {
                                console.log(err);
                            }
                            if (status) {
                                console.log('update model container status success!');
                            } else {
                                console.log('update model container status fail!');
                            }
                        });
                    }
                }
            }
        }else{
            return callback(null,true);
        }
    })
}

//! get by pid(check is any servers available, return the status and other information(such as: Task running nummer))
ServersCtrl.getServerStatusByPid = function(pid, callback){

    ServersCtrl.getByPIDWithStatus(pid, true, function(err, servers){
        if(err){
            return callback(err);
        }
        var status = true;
        if(servers.length < 1){
            status = false;
        }

        // TaskCtrl.getByStartedStatus(function(err,data){
        CommonService.getByStartedStatus(function(err,data){
            if(err){
                return callback(err);
            }
            return callback(null,{
                status: status,
                running: data
            });
        })
    })
}

//! get server by PID and status is true
ServersCtrl.getByPIDWithStatus = function(pid, status, callback){
    ServersModel.getByPIDWithStatus(pid, status, this.returnFunction(callback, 'Error in getting servers by PID and status'));
}

//! get server by type and status is true
ServersCtrl.getByTypeWithStatus = function (type, status, callback) {
    ServersModel.getByTypeWithStatus(type, status, this.returnFunction(callback, 'Error in getting servers by type and status'));
}

//! get model service container status, only used in the situation that the type is 1(Local network)
ServersCtrl.pingAndUpdate = function (url, server, callback) {
    var start_Time = Date.now();
    request.get(url, function (err, data) {
        if (err) {
            server.s_status = false;
            ServersModel.update(server,function(err,result){
                if(err){
                    return callback(err);
                }
                // 新增同步环境状态信息到门户(异步请求任务)
                ServersCtrl.updateContainerStatusToPortal(server, false, 3000, function (err, status) {
                    if (err) {
                        console.log(err);
                    }
                    if (status) {
                        console.log('update model container status success!');
                    } else {
                        console.log('update model container status fail!');
                    }
                });
                return callback(null, false);
            })
        } else {
            let end_Time = Date.now();
            var ping_value = end_Time - start_Time;
            server.s_status = true;
            ServersModel.update(server, function (err, result) {
                if (err) {
                    return callback(err);
                }
                //新增同步环境状态信息到门户(异步请求任务)
                ServersCtrl.updateContainerStatusToPortal(server, true, ping_value, function (err, status) {
                    if (err) {
                        console.log(err);
                    }
                    if (status) {
                        console.log('update model container success!');
                    } else {
                        console.log('update model container fail!');
                    }
                });
                return callback(null, true);
            })
        }
    })
}

//! get local network server(check is any local server available, return the status and other information(example: Task running number))
ServersCtrl.getLocalServers = function (callback) {
    ServersCtrl.getByTypeWithStatus(1, true, function (err, servers) {
        if (err) {
            return callback(err);
        }
        var status = true;
        if (servers.length < 1) {
            status = false;
        }

        // TaskCtrl.getByStartedStatus(function (err, data) {
        CommonService.getByStartedStatus(function (err, data) {
            if (err) {
                return callback(err);
            }
            return callback(null, {
                status: status,
                running: data
            });
        })
    })
}

//! dispatch request to other server
ServersCtrl.dispatchRequest = function (req, server, callback) {
    var url = 'http://' + server.s_ip + ':' + server.s_port + "/modelser?type=1";
    //dispatch
    req.pipe(request.post(url, function (err, response, data) {
        if (err) {
            return callback(err);
        }
        //handle the result
        var body = JSON.parse(data);
        if (body.result === 'suc') {
            return callback(null, {
                host: server.s_ip,
                port: server.s_port,
                msid: body.data._id
            })
        } else {
            return callback(body.message);
        }
    }));
}

//! add by wangming ,update the registered model container's environment information. At the same time, post the details to the portal
ServersCtrl.updateServerEnviroInfo = function (mac, info, callback) {
    ServersModel.getByMac(mac, function (err, server) {
        if (err) {
            return callback(err);
        }
        if (server.length == 0) {
            return callback(null, false);
        }
        //the result which return is Array type, so get the first element
        server = server[0];
        server.s_datetime = new Date();
        server.s_status = true;
        server.s_hardware = info.hardware.static_Info;
        server.s_software = info.software;
        server.s_dynamic = info.hardware.dynamic_Info;
        ServersModel.update(server, function (err, result) {
            if (err) {
                return callback(err);
            }
            ServersCtrl.registerInfoToManagerServer(server, function (err, status) {
                if (err) {
                    console.log(err);
                }
                if (status) {
                    console.log('register success to portal success!');
                } else {
                    console.log('register success to portal fail!');
                }
            });
            return callback(null, server);
        })

    })
}

// ! add by wangming, register the information to the portal
/*ServersCtrl.registerInfoToPortal = function (server, callback) {
    // 获取taskServer在managerServer注册后返回的ID
    RegisterCtrl.getRecord(function (err, record) {
        if (err) {
            return callback(err);
        }
        let param = {
            user: server.s_user,
            software: server.s_software,
            hardware: server.s_hardware,
            ip: server.s_ip,
            mac: server.s_mac,
            t_id: record.r_mid
        };

        let url = 'http://' + Setting.portal.website + '/modelContainer/push';
        request.post(url, { json: true, body: param }, function (err, data) {
            if (err) {
                return callback(err);
            }
            let body = data.body;
            if (body.code != -1) {
                return callback(null, true);
            } else {
                return callback(null, false);
            }
        })
    })
}*/

// ! add by wangming, unregister the information to the portal
ServersCtrl.unregisterInfoToPortal = function (mac, user, callback) {
    let form = {
        user: user,
        mac: mac
    };
    let url = 'http://' + Setting.portal.website + '/modelContainer/remove';
    request.post(url, { form: form }, function (err, data) {
        if (err) {
            return callback(err);
        }
        let body = JSON.parse(data.body);
        if (body.code != -1) {
            return callback(null, true);
        } else {
            return callback(null, false);
        }
    })

}

ServersCtrl.updateContainerStatusToPortal = function (server, status, ping_value, callback) {
    let form = {
        user: server.s_user,
        mac: server.s_mac,
        status: status,
        ping: ping_value
    };
    //! remvoe this function
    return callback(null, true);
    //转向我的管理容器后台
    let url = 'http://' + Setting.resourceCenter.website + '/computer/updateStatus';
    request.post(url, { form: form }, function (err, data) {
        if (err) {
            return callback(err);
        }
        let body = JSON.parse(data.body);
        if (body.code != -1) {
            return callback(null, true);
        } else {
            return callback(null, false);
        }
    })
}

//! add by wangming at 2020.04.30 ,get all available modelService
ServersCtrl.getAllAvailModelServicesPid = function (callback) {
    ServersModel.getAvailable(function (err, servers) {
        if (err) {
            return callback(err);
        }

        let temp = [];
        for (let i = 0; i < servers.length; i++) {
            let s_services = servers[i].s_services;
            for (let j = 0; j < s_services.length; j++) {
                temp.push(s_services[j]);
            }
        }
        //进行去重操作
        let result = _.uniqWith(temp, function (arrVal, othVal) {
            if (arrVal.pid == othVal.pid) {
                return true;
            } else {
                return false;
            }
        });
        return callback(null, result);
    })
}

ServersCtrl.bindServices = function (ip, mac, modelser, callback) {
    ServersCtrl.checkIP(ip, mac, function (err, server) {
        if (err) {
            return callback(err);
        }
        if (server == false) {
            return callback(new Error('No such server'));
        }
        server.s_services.push(modelser);
        ServersModel.update(server, function (err, result) {
            if (err) {
                return callback(err);
            }
            return callback(null, true);
        });
    })
}

ServersCtrl.unbindService = function (ip, mac, modelser, callback) {
    ServersCtrl.checkIP(ip, mac, function (err, server) {
        if (err) {
            return callback(err);
        }
        if (server == false) {
            return callback(new Error('No such server'));
        }
        var servicesArray = server.s_services;
        //从服务数组中删除特定msid和pid的数据项
        var resultArray = _.filter(servicesArray, function (service) {
            if (service.mid != modelser.mid) {
                return true;
            } else {
                return false;
            }
        });
        server.s_services = resultArray;
        ServersModel.update(server, function (err, result) {
            if (err) {
                return callback(err);
            }
            return callback(null, true);
        })

    })
}

ServersCtrl.batchUnbindServices = function (ip, mac, modelsers, callback) {
    ServersCtrl.checkIP(ip, mac, function (err, server) {
        if (err) {
            return callback(err);
        }
        if (server == false) {
            return callback(new Error('No such server'));
        }
        var servicesArray = server.s_services;
        var resultArray = _.filter(servicesArray, function (service) {
            let mid = service.mid;
            for (let i = 0; i < modelsers.length; i++) {
                if (modelsers[i] == mid) {
                    return false;
                } else {
                    return true;
                }
            }
        });

        server.s_services = resultArray;
        ServersModel.update(server, function (err, result) {
            if (err) {
                return callback(err);
            }
            return callback(null, true);
        })
    })
}

//add by wangming at 2020.05.18 register the model container to Manager Server
/*ServersCtrl.registerInfoToManagerServer = function (server, callback) {
    //获取taskServer在managerServer注册后返回的ID
    RegisterCtrl.getRecord(function (err, record) {
        if (err) {
            return callback(err);
        }
        let s_hardware = server.s_hardware;
        //构建传输json所需要的hardware字段
        let hardware = {
            cpu_type: s_hardware.CPU_Type,
            cpu_core: s_hardware.CPU_Core,
            cpu_frequency: s_hardware.CPU_Frequency,
            memory_size: s_hardware.Memory_Size,
            band_width: s_hardware.Band_Width,
            disk_size: s_hardware.Disk_Available,
            disk_all: s_hardware.Disk_All
        };
        let dynamicInfo = {
            cpu_rate: server.s_dynamic.CPU_Rate,
            memory_rate: server.s_dynamic.Memory_Rate
        };
        //默认填选ping值为 10ms
        let param = {
            userId: server.s_user,
            mac: server.s_mac,
            agentId: record.r_mid,
            hostName: s_hardware.HostName,
            ip: server.s_ip,
            port: server.s_port,
            type: server.s_type,
            ping_value: '10',
            platform: s_hardware.Platform,
            version: s_hardware.Version,
            system: s_hardware.System_Type,
            os_bit: s_hardware.OS_Bit,
            software: server.s_software,
            hardware: hardware,
            dynamicInfo: dynamicInfo
        };

        //论文测试利用我的资源管理后台
        let url = 'http://' + Setting.resourceCenter.website + '/computer/add';
        request.post(url, { json: true, body: param }, function (err, data) {
            if (err) {
                return callback(err);
            }
            let body = data.body;
            if (body.code == 0) {
                return callback(null, true);
            } else {
                return callback(null, false);
            }
        })
    })
}*/

//add by wangming at 2020.05.18 unregister the model container to Manager Server
ServersCtrl.unregisterInfoToManagerServer = function (mac, user, callback) {
    let form = {
        userName: user,
        mac: mac
    };
    let url = 'http://' + Setting.resourceCenter.website + '/computer/remove';
    request.post(url, { form: form }, function (err, data) {
        if (err) {
            return callback(err);
        }
        let body = JSON.parse(data.body);
        if (body.code != -1) {
            return callback(null, true);
        } else {
            return callback(null, false);
        }
    });
}

//add by wangming at 2020.05.18 update model container's dynamic info to the Manager Server
ServersCtrl.updateDynamicInfoToManagerServer = function (mac, dynamic_Info, callback) {
    ServersModel.getByMac(mac, function (err, server) {
        if (err) {
            return callback(err);
        }
        if (server.length == 0) {
            return callback(null, false);
        }
        //the result which return is Array type, so get the first element
        server = server[0];
        server.s_datetime = new Date();
        server.s_status = true;
        server.s_dynamic = dynamic_Info;
        ServersModel.update(server, function (err, result) {
            if (err) {
                return callback(err);
            }
            //更新环境信息到Manager Server
            let param = {
                userId: server.s_user,
                mac: server.s_mac,
                dynamicInfo: {
                    cpu_rate: dynamic_Info.CPU_Rate,
                    memory_rate: dynamic_Info.Memory_Rate
                }
            };
            let url = 'http://' + Setting.resourceCenter.website + '/computer/updateDynamicInfo';
            request.post(url, { json: true, body: param }, function (err, data) {
                if (err) {
                    return callback(err);
                }
                let body = data.body;
                if (body.code == 0) {
                    return callback(null, true);
                } else {
                    return callback(null, false);
                }
            })
        })
    })
}

//add by wangming at 2020.05.24  根据pid查询到所有可用的模型容器，同时还需要返回其mac地址
ServersCtrl.getAllSuitableServerByPid = function(pid, callback){
    ServersCtrl.getByPIDWithStatus(pid, true, function(err, servers){
        if(err){
            return callback(err);
        }
        //Task Server是否拥有符合条件的模型容器
        if(servers.length < 1){
            return callback(null, {
                status: false,
                containerInfos: []
            });
        }else {
            var containerInfoList = [];
            var count = 0;
            //获取符合条件模型容器正在开展的计算任务数量
            var pending = function(index){
                count++;
                return function(err, result){
                    count--;
                    if(err){
                        return callback(err);
                    }else{
                        containerInfoList.push({
                            mac: servers[index].s_mac,
                            sid: servers[index]._id,
                            running: result.task,
                            reliability: result.reliability
                        });
                    }
                    if(count == 0){
                        return callback(null, {
                            status: true,
                            containerInfos: containerInfoList
                        });
                    }
                }
            }
            for(var i = 0; i < servers.length;i++){
                let sid = servers[i]._id;
                // TaskCtrl.getAllByServerAndStartedStatus(sid,pid,pending(i));
                CommonService.getAllByServerAndStartedStatus(sid,pid,pending(i));
            }
        }
    })
}

//add running task 
ServersCtrl.addRunningTask = function(){
    
}